from pathlib import Path
from typing import Optional, Type
from io import TextIOWrapper

from chernc.treediff.gumtree.gen.tree_generator import TreeGenerator
from chernc.treediff.gumtree.matchers.mapping_store import MappingStore
from chernc.treediff.gumtree.tree.tree import TreeLike
from chernc.treediff.gumtree.tree.tree_context import TreeContext

from abc import ABC, abstractmethod
from typing import Callable, Optional, Dict, Iterator, Any
import io

from chernc.treediff.gumtree.tree.tree_visitor import TreeVisitor


class AbstractSerializer(ABC):

    @abstractmethod
    def write_to(self, writer: io.TextIOWrapper) -> None:
        """Write to the specified writer."""
        pass

    def write_to_output_stream(self, writer: io.BufferedWriter) -> None:
        """Write to the given output stream, ensuring it's in UTF-8 encoding."""
        with io.TextIOWrapper(writer, encoding="utf-8") as os:
            self.write_to(os)

    def __str__(self) -> str:
        try:
            with io.StringIO() as s:
                self.write_to(s)
                return s.getvalue()
        except Exception as e:
            raise RuntimeError(e)

    def write_to_file(self, file: str) -> None:
        """Write to a file."""
        with open(file, "w", encoding="utf-8") as w:
            self.write_to(w)

    def write_to_file_obj(self, file: "Path") -> None:
        """Write to a file from a File object."""
        with open(file, "w", encoding="utf-8") as w:
            self.write_to(w)


class TreeFormatter(ABC):
    @abstractmethod
    def start_serialization(self) -> None:
        pass

    @abstractmethod
    def end_prolog(self) -> None:
        pass

    @abstractmethod
    def stop_serialization(self) -> None:
        pass

    @abstractmethod
    def start_tree(self, tree: TreeLike) -> None:
        pass

    @abstractmethod
    def end_tree_prolog(self, tree: TreeLike) -> None:
        pass

    @abstractmethod
    def end_tree(self, tree: TreeLike) -> None:
        pass

    @abstractmethod
    def close(self) -> None:
        pass

    @abstractmethod
    def serialize_attribute(self, name: str, value: str) -> None:
        pass


class TreeFormatterAdapter(TreeFormatter):
    def __init__(self, ctx: "TreeContext"):
        self.context = ctx

    def start_serialization(self) -> None:
        """Start the serialization process."""
        pass

    def end_prolog(self) -> None:
        """End the prolog section."""
        pass

    def start_tree(self, tree: TreeLike) -> None:
        """Start serializing a tree."""
        pass

    def end_tree_prolog(self, tree: TreeLike) -> None:
        """End the prolog section for the tree."""
        pass

    def end_tree(self, tree: TreeLike) -> None:
        """End serializing the tree."""
        pass

    def stop_serialization(self) -> None:
        """Stop the serialization process."""
        pass

    def close(self) -> None:
        """Close any resources used during serialization."""
        pass

    def serialize_attribute(self, name: str, value: str) -> None:
        """Serialize an attribute."""
        pass


class AbstractTextFormatter(TreeFormatterAdapter):
    def __init__(self, writer: TextIOWrapper, ctx: "TreeContext"):
        super().__init__(ctx)
        self.writer = writer
        self.level = 0

    def indent(self, level: int, prefix: str) -> None:
        """Write indentation to the writer based on the level."""
        for _ in range(level):
            self.writer.write(prefix)

    def start_tree(self, tree: TreeLike) -> None:
        """Start writing a tree."""
        if self.level != 0:
            self.writer.write("\n")
        self.indent(self.level, "    ")
        self.level += 1

        self.write_tree(tree)

    @abstractmethod
    def write_tree(self, tree: TreeLike) -> None:
        """Write the tree to the output stream."""
        pass

    def end_tree(self, tree: TreeLike) -> None:
        """End writing a tree."""
        self.level -= 1


class TextFormatter(AbstractTextFormatter):
    def __init__(self, writer: TextIOWrapper, ctx: "TreeContext"):
        super().__init__(writer, ctx)

    def write_tree(self, tree: TreeLike) -> None:
        """Write the tree to the writer."""
        self.writer.write(str(tree))


class ShortTextFormatter(AbstractTextFormatter):
    def __init__(self, writer: TextIOWrapper, ctx: "TreeContext"):
        super().__init__(writer, ctx)

    def write_tree(self, tree: TreeLike) -> None:
        """Write the tree to the writer in a shortened format."""
        self.writer.write(str(tree))


class TreeSerializer(AbstractSerializer):
    def __init__(self, ctx: Optional[TreeContext], root: TreeLike, formatter: Type[TreeFormatterAdapter]):
        self.new_formatter = formatter
        self.context: Optional[TreeContext] = ctx
        self.root = root
        # self.serializers = MetadataSerializers()
        # if ctx is not None:
        #     self.serializers.add_all(ctx.get_serializers())

    def to_string(self) -> str:
        result = ""
        
    
    def write_to(self, writer: io.TextIOWrapper) -> None:
        formatter = self.new_formatter(writer, self.context)
        try:
            self.write_tree(formatter, self.root)
        finally:
            formatter.close()

    def forward_exception(self, e: Exception) -> None:
        raise FormatException(e)

    def write_tree(self, formatter: "TreeFormatter", root: TreeLike) -> None:
        formatter.start_serialization()
        if self.context:
            self.write_attributes(formatter, self.context.get_metadata())
        formatter.end_prolog()
        try:
            TreeVisitor.visit_tree(root, TreeVisitorCallback(formatter))
        except FormatException as e:
            raise e.get_cause()
        formatter.stop_serialization()

    def write_attributes(self, formatter: "TreeFormatter", it: Iterator) -> None:
        for entry in it:
            self.serializers.serialize(formatter, entry[0], entry[1])

    def export(self, name: str, serializer: "MetadataSerializer") -> "TreeSerializer":
        self.serializers.add(name, serializer)
        return self

    def export_multiple(self, *names: str) -> "TreeSerializer":
        for name in names:
            self.serializers.add(name, str)
        return self


class MetadataSerializer(Callable):
    def __call__(self, obj: Any) -> str:
        return str(obj)


class MetadataUnserializer(Callable):
    def __call__(self, value: str) -> Any:
        return value


class FormatException(Exception):
    def __init__(self, cause: Exception):
        super().__init__(cause)
        self.cause = cause

    def get_cause(self) -> Exception:
        return self.cause


class TreeIoUtils:
    @staticmethod
    def from_xml() -> "TreeGenerator":
        return XmlInternalGenerator()

    @staticmethod
    def from_xml_with_unserializers(unserializers) -> "TreeGenerator":
        generator = XmlInternalGenerator()
        generator.get_unserializers().extend(unserializers)
        return generator

    @staticmethod
    def to_xml(ctx: "TreeContext", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, XmlFormatter)

    @staticmethod
    def to_annotated_xml(ctx: "TreeContext", is_src: bool, m: "MappingStore", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, XmlAnnotatedFormatter, is_src, m)

    @staticmethod
    def to_compact_xml(ctx: "TreeContext", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, XmlCompactFormatter)

    @staticmethod
    def to_json(ctx: "TreeContext", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, JsonFormatter)

    @staticmethod
    def to_lisp(ctx: "TreeContext", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, LispFormatter)

    @staticmethod
    def to_dot(ctx: "TreeContext", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, DotFormatter)

    @staticmethod
    def to_text(ctx: "TreeContext", root: TreeLike = None) -> "TreeSerializer":
        if root is None:
            root = ctx.get_root()
        return TreeSerializer(ctx, root, TextFormatter)

    @staticmethod
    def to_short_text(root: TreeLike) -> "TreeSerializer":
        return TreeSerializer(None, root, ShortTextFormatter)
